/*----------------------------------------------------------------------------
 * Tencent is pleased to support the open source community by making TencentOS
 * available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 * If you have downloaded a copy of the TencentOS binary from Tencent, please
 * note that the TencentOS binary is licensed under the BSD 3-Clause License.
 *
 * If you have downloaded a copy of the TencentOS source code from Tencent,
 * please note that TencentOS source code is licensed under the BSD 3-Clause
 * License, except for the third-party components listed below which are
 * subject to different license terms. Your integration of TencentOS into your
 * own projects may require compliance with the BSD 3-Clause License, as well
 * as the other licenses applicable to the third-party components included
 * within TencentOS.
 *---------------------------------------------------------------------------*/

#include "tos_k.h"

__STATIC_INLINE__ void task_reset(k_task_t *task)
{
#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u
	knl_object_alloc_reset(&task->knl_obj);

	tos_list_init(&task->dead_list);
#endif
	tos_list_init(&task->stat_list);
	tos_list_init(&task->tick_list);
	tos_list_init(&task->pend_list);

#if TOS_CFG_MUTEX_EN > 0u
	tos_list_init(&task->mutex_own_list);
	task->prio_pending = K_TASK_PRIO_INVALID;
#endif

	task->pend_state = PEND_STATE_NONE;
	task->pending_obj = (pend_obj_t *) K_NULL;

#if TOS_CFG_MESSAGE_QUEUE_EN > 0u
	task->msg = K_NULL;
#endif

#if TOS_CFG_MAIL_QUEUE_EN > 0u
	task->mail = K_NULL;
	task->mail_size = 0;
#endif

	TOS_OBJ_DEINIT(task);
}

__STATIC__ void task_exit(void)
{
	tos_task_destroy(K_NULL);
}

#if TOS_CFG_MUTEX_EN > 0u
__STATIC__ k_prio_t task_highest_pending_prio_get(k_task_t *task)
{
	k_mutex_t *mutex;
	k_prio_t prio, highest_prio_pending = K_TASK_PRIO_INVALID;

	TOS_LIST_FOR_EACH_ENTRY(mutex, k_mutex_t, owner_anchor, &task->mutex_own_list)
	{
		prio = pend_highest_pending_prio_get(&mutex->pend_obj);
		if (prio < highest_prio_pending)
		{
			highest_prio_pending = prio;
		}
	}

	return highest_prio_pending;
}

__STATIC__ void task_mutex_release(k_task_t *task)
{
	k_mutex_t *mutex, *tmp;

	TOS_LIST_FOR_EACH_ENTRY_SAFE(mutex, tmp, k_mutex_t, owner_anchor, &task->mutex_own_list)
	{
		mutex_release(mutex);
	}
}
#endif

__API__ k_err_t tos_task_create(k_task_t *task, char *name,
		k_task_entry_t entry, void *arg, k_prio_t prio, k_stack_t *stk_base,
		size_t stk_size, k_timeslice_t timeslice)
{
	TOS_CPU_CPSR_ALLOC();

	TOS_IN_IRQ_CHECK();

	TOS_PTR_SANITY_CHECK(task);
	TOS_PTR_SANITY_CHECK(entry);
	TOS_PTR_SANITY_CHECK(stk_base);

	/* try to re-create a task, kind of dangerous */
	TOS_OBJ_TEST_RC(task, KNL_OBJ_TYPE_TASK, K_ERR_TASK_ALREADY_CREATED);

	if (unlikely(stk_size < K_TASK_STK_SIZE_MIN))
	{
		return K_ERR_TASK_STK_SIZE_INVALID;
	}

	if (unlikely(prio == K_TASK_PRIO_IDLE && !knl_is_idle(task)))
	{
		return K_ERR_TASK_PRIO_INVALID;
	}

	if (unlikely(prio > K_TASK_PRIO_IDLE))
	{
		return K_ERR_TASK_PRIO_INVALID;
	}

	task_reset(task);
	tos_list_add(&task->stat_list, &k_stat_list);

	TOS_OBJ_INIT(task, KNL_OBJ_TYPE_TASK);
#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u
	knl_object_alloc_set_static(&task->knl_obj);
#endif

	task->sp = cpu_task_stk_init((void *) entry, arg, (void *) task_exit,
			stk_base, stk_size);
	task->entry = entry;
	task->arg = arg;
	task->prio = prio;
	task->stk_base = stk_base;
	task->stk_size = stk_size;
	strncpy(task->name, name, K_TASK_NAME_LEN_MAX);

#if TOS_CFG_ROUND_ROBIN_EN > 0u
	task->timeslice_reload = timeslice;

	if (timeslice == (k_timeslice_t) 0u)
	{
		task->timeslice = k_robin_default_timeslice;
	}
	else
	{
		task->timeslice = timeslice;
	}
#endif

	TOS_CPU_INT_DISABLE()
	;
	task_state_set_ready(task);
	readyqueue_add_tail(task);
	TOS_CPU_INT_ENABLE()
	;

	if (tos_knl_is_running())
	{
		knl_sched();
	}

	return K_ERR_NONE;
}

__STATIC__ k_err_t task_do_destroy(k_task_t *task)
{
	TOS_CPU_CPSR_ALLOC();

	if (knl_is_idle(task))
	{
		return K_ERR_TASK_DESTROY_IDLE;
	}

	TOS_CPU_INT_DISABLE()
	;

#if TOS_CFG_MUTEX_EN > 0u
	// when we die, wakeup all the people in this land.
	if (!tos_list_empty(&task->mutex_own_list))
	{
		task_mutex_release(task);
	}
#endif

	if (task_state_is_ready(task))
	{ // that's simple, good kid
		readyqueue_remove(task);
	}
	if (task_state_is_sleeping(task))
	{
		tick_list_remove(task);
	}
	if (task_state_is_pending(task))
	{
		pend_list_remove(task);
	}

	tos_list_del(&task->stat_list);
	task_reset(task);

	task_state_set_deleted(task);

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__STATIC__ k_err_t task_destroy_static(k_task_t *task)
{
#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u
	if (!knl_object_alloc_is_static(&task->knl_obj))
	{
		return K_ERR_OBJ_INVALID_ALLOC_TYPE;
	}
#endif

	return task_do_destroy(task);
}

#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u

__STATIC__ void task_free(k_task_t *task)
{
	tos_mmheap_free(task->stk_base);
	tos_mmheap_free(task);
}

__KNL__ void task_free_all(void)
{
	TOS_CPU_CPSR_ALLOC();
	k_task_t *task, *tmp;

	TOS_CPU_INT_DISABLE()
	;

	TOS_LIST_FOR_EACH_ENTRY_SAFE(task, tmp, k_task_t, dead_list, &k_dead_task_list)
	{
		tos_list_del(&task->dead_list);
		task_free(task);
	}

	TOS_CPU_INT_ENABLE()
	;
}

__API__ k_err_t tos_task_create_dyn(k_task_t **task, char *name,
		k_task_entry_t entry, void *arg, k_prio_t prio, size_t stk_size,
		k_timeslice_t timeslice)
{
	k_err_t err;
	k_task_t *the_task;
	k_stack_t *stk_base;

	TOS_IN_IRQ_CHECK();

	TOS_PTR_SANITY_CHECK(task);
	TOS_PTR_SANITY_CHECK(entry);

	if (unlikely(stk_size < sizeof(cpu_context_t)))
	{
		return K_ERR_TASK_STK_SIZE_INVALID;
	}

	if (unlikely(prio == K_TASK_PRIO_IDLE))
	{
		return K_ERR_TASK_PRIO_INVALID;
	}

	if (unlikely(prio > K_TASK_PRIO_IDLE))
	{
		return K_ERR_TASK_PRIO_INVALID;
	}

	the_task = tos_mmheap_calloc(1, sizeof(k_task_t));
	if (!the_task)
	{
		return K_ERR_TASK_OUT_OF_MEMORY;
	}

	stk_base = tos_mmheap_alloc(stk_size);
	if (!stk_base)
	{
		tos_mmheap_free(the_task);
		return K_ERR_TASK_OUT_OF_MEMORY;
	}

	the_task->stk_base = stk_base;
	err = tos_task_create(the_task, name, entry, arg, prio, stk_base, stk_size,
			timeslice);
	if (err != K_ERR_NONE)
	{
		task_free(the_task);
		return err;
	}

	knl_object_alloc_set_dynamic(&the_task->knl_obj);

	*task = the_task;

	return K_ERR_NONE;
}

__STATIC__ k_err_t task_destroy_dyn(k_task_t *task)
{
	k_err_t err;

	tos_knl_sched_lock();

	err = task_do_destroy(task);
	if (err != K_ERR_NONE)
	{
		tos_knl_sched_unlock();
		return err;
	}

	if (knl_is_self(task))
	{ // we are destroying ourself
		// in this situation, we cannot just free ourself's task stack because we are using it
		// we count on the idle task to free the memory
		tos_list_add(&task->dead_list, &k_dead_task_list);
	}
	else
	{
		task_free(task);
	}

	tos_knl_sched_unlock();

	return K_ERR_NONE;
}

#endif

__API__ k_err_t tos_task_destroy(k_task_t *task)
{
	TOS_IN_IRQ_CHECK();

	if (unlikely(!task))
	{
		task = k_curr_task;
	}

	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	if (knl_is_self(task) && knl_is_sched_locked())
	{
		return K_ERR_SCHED_LOCKED;
	}

#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u
	if (knl_object_alloc_is_dynamic(&task->knl_obj))
	{
		return task_destroy_dyn(task);
	}
#endif

	return task_destroy_static(task);
}

__API__ void tos_task_yield(void)
{
	TOS_CPU_CPSR_ALLOC();

	if (knl_is_inirq())
	{
		return;
	}

	TOS_CPU_INT_DISABLE()
	;

	readyqueue_remove(k_curr_task);
	readyqueue_add_tail(k_curr_task);

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();
}

__API__ k_err_t tos_task_prio_change(k_task_t *task, k_prio_t prio_new)
{
	TOS_CPU_CPSR_ALLOC();
#if TOS_CFG_MUTEX_EN > 0u
	k_prio_t highest_pending_prio;
#endif

	TOS_IN_IRQ_CHECK();
	TOS_PTR_SANITY_CHECK(task);
	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	if (unlikely(prio_new >= K_TASK_PRIO_IDLE))
	{
		return K_ERR_TASK_PRIO_INVALID;
	}

	TOS_CPU_INT_DISABLE()
	;

	if (task->prio == prio_new)
	{ // just kidding
		TOS_CPU_INT_ENABLE()
		;
		knl_sched();
		return K_ERR_NONE;
	}

#if TOS_CFG_MUTEX_EN > 0u
	if (!tos_list_empty(&task->mutex_own_list))
	{
		highest_pending_prio = task_highest_pending_prio_get(task);
		if (prio_new > highest_pending_prio)
		{
			task->prio_pending = prio_new;
			prio_new = highest_pending_prio;
		}
	}
#endif

	if (task_state_is_pending(task))
	{
		task->prio = prio_new;
		pend_list_adjust(task);
	}
	else if (task_state_is_sleeping(task))
	{
		task->prio = prio_new;
	}
	else if (task_state_is_ready(task))
	{ // good kid
		readyqueue_remove(task);

		/* ATTENTION:
		 must do the prio assignment after readyqueue_remove
		 otherwise the k_rdyq.highest_prio refresh in readyqueue_remove will be wrong.
		 */
		task->prio = prio_new;
		if (knl_is_self(task))
		{
			readyqueue_add_head(task);
		}
		else
		{
			readyqueue_add_tail(task);
		}
	}

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__API__ k_err_t tos_task_suspend(k_task_t *task)
{
	TOS_CPU_CPSR_ALLOC();

	if (unlikely(!task))
	{
		task = k_curr_task;
	}

	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	if (knl_is_idle(task))
	{
		return K_ERR_TASK_SUSPEND_IDLE;
	}

	if (unlikely(knl_is_self(task)) && knl_is_sched_locked())
	{ // if not you, who?
		return K_ERR_SCHED_LOCKED;
	}

	TOS_CPU_INT_DISABLE()
	;

	if (task_state_is_ready(task))
	{ // kill the good kid
		readyqueue_remove(task);
	}
	task_state_set_suspended(task);

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__API__ k_err_t tos_task_resume(k_task_t *task)
{
	TOS_CPU_CPSR_ALLOC();

	TOS_PTR_SANITY_CHECK(task);
	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	if (unlikely(knl_is_self(task)))
	{
		return K_ERR_TASK_RESUME_SELF;
	}

	TOS_CPU_INT_DISABLE()
	;

	if (!task_state_is_suspended(task))
	{
		TOS_CPU_INT_ENABLE()
		;
		knl_sched();
		return K_ERR_NONE;
	}

	task_state_reset_suspended(task);
	if (task_state_is_ready(task))
	{ // we are good kid now
		readyqueue_add(task);
	}

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__API__ k_err_t tos_task_delay(k_tick_t delay)
{
	TOS_CPU_CPSR_ALLOC();

	TOS_IN_IRQ_CHECK();

	if (knl_is_sched_locked())
	{
		return K_ERR_SCHED_LOCKED;
	}

	if (unlikely(delay == (k_tick_t )0u))
	{
		tos_task_yield();
		return K_ERR_NONE;
	}

	if (unlikely(delay == TOS_TIME_FOREVER))
	{
		// if you wanna delay your task forever, why don't just suspend?
		return K_ERR_DELAY_FOREVER;
	}

	TOS_CPU_INT_DISABLE()
	;

	tick_list_add(k_curr_task, delay);
	readyqueue_remove(k_curr_task);

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__API__ k_err_t tos_task_delay_abort(k_task_t *task)
{
	TOS_CPU_CPSR_ALLOC();

	TOS_IN_IRQ_CHECK();
	TOS_PTR_SANITY_CHECK(task);
	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	TOS_CPU_INT_DISABLE()
	;

	if (knl_is_self(task) || !task_state_is_sleeping(task))
	{
		TOS_CPU_INT_ENABLE()
		;
		return K_ERR_TASK_NOT_DELAY;
	}

	if (task_state_is_suspended(task))
	{
		TOS_CPU_INT_ENABLE()
		;
		return K_ERR_TASK_SUSPENDED;
	}

	tick_list_remove(task);
	readyqueue_add(task);

	TOS_CPU_INT_ENABLE()
	;
	knl_sched();

	return K_ERR_NONE;
}

__API__ k_task_t *tos_task_curr_task_get(void)
{
	TOS_CPU_CPSR_ALLOC();
	k_task_t *curr_task = K_NULL;

	TOS_CPU_INT_DISABLE()
	;
	if (likely(tos_knl_is_running()))
	{
		curr_task = k_curr_task;
	}
	TOS_CPU_INT_ENABLE()
	;

	return curr_task;
}

__API__ void tos_task_walkthru(k_task_walker_t walker)
{
	TOS_CPU_CPSR_ALLOC();
	k_task_t *task;

	if (!walker)
	{
		return;
	}

	TOS_CPU_INT_DISABLE()
	;

	TOS_LIST_FOR_EACH_ENTRY(task, k_task_t, stat_list, &k_stat_list)
	{
		walker(task);
	}

	TOS_CPU_INT_ENABLE()
	;
}

__DEBUG__ void tos_task_info_display(void)
{
	tos_task_walkthru(task_default_walker);
}

#if TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN > 0u

__API__ k_err_t tos_task_stack_draught_depth(k_task_t *task, int *depth)
{
	TOS_CPU_CPSR_ALLOC();
	k_err_t rc;

	TOS_PTR_SANITY_CHECK(depth);

	if (unlikely(!task))
	{
		task = k_curr_task;
	}

	TOS_OBJ_VERIFY(task, KNL_OBJ_TYPE_TASK);

	TOS_CPU_INT_DISABLE();
	rc = cpu_task_stack_draught_depth(task->stk_base, task->stk_size, depth);
	TOS_CPU_INT_ENABLE();

	return rc;
}

#endif

